/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#define ITK_TEMPLATE_EXPLICIT_TransformFileReader
#include "itkTransformFileReader.h"
#include "itkTransformIOFactory.h"
#include "itkCompositeTransformIOHelper.h"
#include "itkKernelTransform.h"

#include "itksys/SystemTools.hxx"


#ifndef ITK_TRANSFORM_FACTORY_MAX_DIM
#define ITK_TRANSFORM_FACTORY_MAX_DIM 9
#endif


namespace itk
{

namespace
{
// Class to initialize kernel transform for dimension D and lower
template< typename TParameterType, unsigned int Dimension>
struct KernelTransformHelper
{
  static int InitializeWMatrix(const typename TransformBaseTemplate<TParameterType>::Pointer &transform)
    {
    if( transform->GetInputSpaceDimension() == Dimension)
      {
      using KernelTransformType = typename itk::KernelTransform< TParameterType, Dimension >;
      auto * kernelTransform = static_cast<KernelTransformType*>(transform.GetPointer());
      kernelTransform->ComputeWMatrix();
      return 0;
      }
    else
      {
      // try one less dimension
      return KernelTransformHelper<TParameterType, Dimension-1>::InitializeWMatrix(transform);
      }
    }
};

// Template specialized class to stop initializing Kernel Transfoms.
template<typename TParameterType>
struct KernelTransformHelper<TParameterType, 0>
{
  static int InitializeWMatrix(const typename TransformBaseTemplate<TParameterType>::Pointer &itkNotUsed(transform))
    {
    return 1;
    }
};
}


template<typename TParametersValueType>
TransformFileReaderTemplate<TParametersValueType>
::TransformFileReaderTemplate() :
  m_FileName("") /* to be removed soon. See .h */
{
}


template<typename TParametersValueType>
TransformFileReaderTemplate<TParametersValueType>
::~TransformFileReaderTemplate() = default;


template<typename TParametersValueType>
void TransformFileReaderTemplate<TParametersValueType>
::Update()
{
  if ( m_FileName.empty() )
    {
    itkExceptionMacro ("No file name given");
    }

  if( m_TransformIO.IsNull() )
    {
    using TransformFactoryIOType = TransformIOFactoryTemplate< TParametersValueType >;
    m_TransformIO = TransformFactoryIOType::CreateTransformIO( m_FileName.c_str(), /*TransformIOFactoryTemplate<TParametersValueType>::*/ ReadMode );
    if ( m_TransformIO.IsNull() )
      {
      std::ostringstream msg;
      msg << "Could not create Transform IO object for reading file "  << this->GetFileName() << std::endl;

      if ( !itksys::SystemTools::FileExists( m_FileName.c_str() ) )
        {
        msg << " File does not exists!";
        }

      std::list< LightObject::Pointer > allobjects =  ObjectFactoryBase::CreateAllInstance("itkTransformIOBaseTemplate");

      if (!allobjects.empty())
        {
        msg << "  Tried to create one of the following:" << std::endl;
        for (auto & allobject : allobjects)
          {
          const Object *obj = dynamic_cast<Object*>(allobject.GetPointer());
          msg << "    " << obj->GetNameOfClass() << std::endl;
          }
        msg << "  You probably failed to set a file suffix, or" << std::endl;
        msg << "    set the suffix to an unsupported type." << std::endl;
        }
      else
        {
        msg << "  There are no registered Transform IO factories." << std::endl;
        msg << "  Please visit https://www.itk.org/Wiki/ITK/FAQ#NoFactoryException to diagnose the problem." << std::endl;
        }

      itkExceptionMacro( << msg.str().c_str() );
      }


    }

  typename TransformIOType::TransformListType & ioTransformList = m_TransformIO->GetTransformList();
  // Clear old results.
  ioTransformList.clear();

  m_TransformIO->SetFileName(m_FileName);
  m_TransformIO->Read();

  if (ioTransformList.empty())
    {
    std::ostringstream msg;
    msg <<  "Transform IO: " << m_TransformIO->GetNameOfClass() << std::endl
        <<  "   failed to read file: " << this->GetFileName() << std::endl;
    itkExceptionMacro( << msg.str() );
    }

  // Clear old results.
  this->m_TransformList.clear();
  // If the transform is derived from itk::KernelTransform, the internal matrices
  // need to be initialized using the transform parameters.
  // kernelTransform->ComputeWMatrix() has to be called after the transform is read but
  // before the transform is used.
  std::string transformTypeName = ioTransformList.front()->GetNameOfClass();
  const size_t len = strlen("KernelTransform");// Computed at compile time in most cases
  if (transformTypeName.size() >= len
      && !transformTypeName.compare(transformTypeName.size()-len , len, "KernelTransform"))
    {
    if( KernelTransformHelper<TParametersValueType,
        ITK_TRANSFORM_FACTORY_MAX_DIM>::InitializeWMatrix(ioTransformList.front().GetPointer()))
      {
      itkDebugMacro(<< "KernelTransform with dimension "
                    << ioTransformList.front()->GetInputSpaceDimension()
                    << " is not automatically initialized. \"ComputeWMatrix()\""
                       " method has to be called."
                   );
      }
    }

  // In the case where the first transform in the list is a
  // CompositeTransform, add all the transforms to that first
  // transform. and return a single composite item on the
  // m_TransformList
  const std::string firstTransformName = ioTransformList.front()->GetNameOfClass();
  if(firstTransformName.find("CompositeTransform") != std::string::npos)
    {
    typename TransformListType::const_iterator tit = ioTransformList.begin();
    typename TransformType::Pointer composite = (*tit).GetPointer();

    // CompositeTransformIOHelperTemplate knows how to assign to the composite
    // transform's internal list
    CompositeTransformIOHelperTemplate<TParametersValueType> helper;
    helper.SetTransformList(composite,ioTransformList);

    this->m_TransformList.push_back( composite );
    }
  else  //Just return the entire list of elements
    {
    for ( auto it = ioTransformList.begin();
         it != ioTransformList.end(); ++it )
      {
      this->m_TransformList.push_back( (*it).GetPointer() );
      }
    }
}


template<typename TParametersValueType>
void TransformFileReaderTemplate<TParametersValueType>
::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "FileName: " << m_FileName << std::endl;
}

#ifdef ITK_HAS_GCC_PRAGMA_DIAG_PUSHPOP
  ITK_GCC_PRAGMA_DIAG_PUSH()
#endif
ITK_GCC_PRAGMA_DIAG(ignored "-Wattributes")

template class ITKIOTransformBase_EXPORT TransformFileReaderTemplate< double >;
template class ITKIOTransformBase_EXPORT TransformFileReaderTemplate< float >;

#ifdef ITK_HAS_GCC_PRAGMA_DIAG_PUSHPOP
  ITK_GCC_PRAGMA_DIAG_POP()
#else
  ITK_GCC_PRAGMA_DIAG(warning "-Wattributes")
#endif

}  // end namespace itk
